//
//  CantripDisplayView.m
//  Cantrip
//
//  Created by James Callender on Tue Apr 30 2002.
//  Copyright (c) 2001 James Callender. All rights reserved.
//

#import "CantripDisplayView.h"

@interface CantripDisplayView (PrivateMethods)

- (void)applyAttributesToString:(NSMutableAttributedString *)string range:(NSRange)range;
- (void)interpretDisplayParams:(unichar)ch;
- (void)processString:(NSString *)str;
- (void)updateColumnForString:(NSString *)string;

@end


@implementation CantripDisplayView

- (id)initWithFrame:(NSRect)frame 
{
    if (self = [super initWithFrame:frame])
    {
	NSLayoutManager *layout = [[NSLayoutManager alloc] init];
	NSTextStorage *storage = [[NSTextStorage alloc] initWithString:@"\n"];
	NSTextContainer *container;
	NSSize contentSize;
	
	_splitView = [[[CantripStaticSplitView alloc] initWithFrame:[self bounds]] autorelease];
	[_splitView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
	[_splitView setIsPaneSplitter:YES];
	[_splitView setStaticIndex:0];
	[_splitView setDelegate:self];
	[_splitView setDividerThickness:5];
	
	// Setup scroll view
	_scrollView = [[[CantripScrollView alloc] initWithFrame:[self bounds]] autorelease];
	contentSize = [_scrollView contentSize];
	[_scrollView setBorderType:NSNoBorder];
	[_scrollView setHasVerticalScroller:YES];
	[_scrollView setHasHorizontalScroller:NO];
	[_scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
	[_scrollView setBackgroundColor:[NSColor clearColor]];
	[_scrollView setDrawsBackground:NO];
	[[_scrollView contentView] setDrawsBackground:NO];
	[_scrollView setTopAction:@selector(toggleSplitAction:)];
	[_scrollView setBottomAction:@selector(toggleLockAction:)];
	[_scrollView setDelegate:self];
	
	// Setup layout manager
	[storage addLayoutManager:layout];
	container = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(contentSize.width, contentSize.height)];
	[layout addTextContainer:container];
	
	// Setup text view
	_textView = [[CantripURLTextView alloc] initWithFrame:
	    NSMakeRect(0, 0, contentSize.width - [NSScroller scrollerWidth], contentSize.height)];
	[_textView setMinSize:NSMakeSize(0, contentSize.height)];
	[_textView setVerticallyResizable:YES];
	[_textView setHorizontallyResizable:NO];
	[_textView setAutoresizingMask:NSViewWidthSizable];
	[_textView setFont:[NSFont fontWithName:@"Courier" size:12]];
	[_textView setBackgroundColor:[NSColor blackColor]];
	[_textView setTextColor:[NSColor whiteColor]];
	[_textView setString:@"\n"];
	[_textView setEditable:NO];
	[[_textView textContainer] setWidthTracksTextView:YES];
	[_textView setDelegate:self];
	
	if (NSAppKitVersionNumber > NSAppKitVersionNumber10_2_3) {
	    [_textView setUsesFindPanel:YES];
	}
	
	[self setTextColor:[NSColor whiteColor]];
	[self setBackgroundColor:[NSColor blackColor]];
	[self setFont:[NSFont fontWithName:@"Courier" size:12]];
	
	[container release];
	[layout release];
	
	[_scrollView setDocumentView:_textView];
	[_splitView addSubview:_scrollView];
	[self addSubview:_splitView];
	
	_stateMachine = [[CantripStateMachine alloc] init];
	_foreColor = nil;
	_backColor = nil;
	_numberOfLines = 0;
	
	mAnsiPlainColors = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
	    [NSColor darkGrayColor], @"black", 
	    [NSColor lightGrayColor], @"white", 
	    [NSColor blueColor], @"blue",
	    [NSColor greenColor], @"green", 
	    [NSColor redColor], @"red", 
	    [NSColor magentaColor], @"magenta", 
	    [NSColor yellowColor], @"yellow", 
	    [NSColor cyanColor], @"cyan",  nil];
	mAnsiIntenseColors = [[NSMutableDictionary alloc] init];
	[mAnsiIntenseColors setDictionary:[mAnsiPlainColors mutableCopy]];
	
	mUsesIntenseColors = NO;
	mBoldIntenseColors = NO;
	mAnsiEnabled = YES;
	mSensativeAutoScroll = YES;
	mIsLocked = NO;
	
	[self setTextColor:[NSColor whiteColor]];
	[self setBackgroundColor:[NSColor blackColor]];
	
	mScrollBack = -1;

	_top = nil;
    }
    return self;
}

- (void)dealloc
{
    [_textView release];
    _textView = nil;
    [_stateMachine release];
    _stateMachine = nil;
    [mAnsiPlainColors release];
    mAnsiPlainColors = nil;
    [mAnsiIntenseColors release];
    mAnsiIntenseColors = nil;
    if (_top) 
    {
	[_top release];
	_top = nil;
    }
    [super dealloc];
}


- (void)drawRect:(NSRect)rect 
{
    [super drawRect:rect];
}

- (void)toggleSplitAction:(id)sender
{
    if ([_scrollView topState]) [self closeSplit];
    else [self openSplit];
}

- (void)openSplit
{
    if (_top == nil)
    {
	NSScrollView *scrollView;
	CantripURLTextView *textView;
	NSLayoutManager *layoutManger = [[NSLayoutManager alloc] init];
	NSTextContainer *container;
	
	container = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(FLT_MAX, FLT_MAX)];
	[container setWidthTracksTextView:YES];
	
	[layoutManger addTextContainer:[container autorelease]];
	
	[[_textView textStorage] addLayoutManager:[layoutManger autorelease]];
	
	_splitSize = NSMakeSize([self bounds].size.width, 100);
	scrollView = [[NSScrollView alloc] initWithFrame:NSMakeRect(0.0, 0.0, _splitSize.width, _splitSize.height)];
	[scrollView setBorderType:NSNoBorder];
	[scrollView setHasVerticalScroller:YES];
	[scrollView setHasHorizontalScroller:NO];
	[scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
	[scrollView setBackgroundColor:[NSColor clearColor]];
	[scrollView setDrawsBackground:NO];
	[[scrollView contentView] setDrawsBackground:NO];

	textView = [[NSTextView alloc] initWithFrame:
		NSMakeRect(0,0,[scrollView frame].size.width - [NSScroller scrollerWidth], [scrollView frame].size.height)
		textContainer:container];
	[textView setMinSize:[scrollView contentSize]];
	[textView setMaxSize:NSMakeSize(1.0e7, 1.0e7)];
	[textView setVerticallyResizable:YES];
	[textView setHorizontallyResizable:NO];
	[textView setAutoresizingMask:NSViewWidthSizable];
	[textView setBackgroundColor:[self backgroundColor]];

	[scrollView setDocumentView:textView];
	[[scrollView contentView] setAutoresizesSubviews:YES];
					
	_top = textView;
    }

    [_splitView addSubview:[_top enclosingScrollView] positioned:NSWindowBelow relativeTo:_scrollView];

    [[_top enclosingScrollView] setFrameSize:_splitSize];
    [[_textView enclosingScrollView] setFrameSize:
	NSMakeSize([_splitView frame].size.width, [_splitView frame].size.height - _splitSize.height - [_splitView dividerThickness])];
    [[_textView enclosingScrollView] setFrameOrigin:
	NSMakePoint(0, [[_top enclosingScrollView] frame].size.height + [_splitView dividerThickness])];

    [_splitView setStaticIndex:1];
    [_splitView display];
    [self autoscroll];
    [self setNeedsDisplay:YES];
}

- (void)closeSplit
{
    _splitSize = [[_top enclosingScrollView] frame].size;
    [[_top enclosingScrollView] removeFromSuperview];
    [_splitView setStaticIndex:0];
}

- (void)toggleLockAction:(id)sender
{
    if ([sender bottomState]) [self setLocked:NO];
    else [self setLocked:YES];
}

- (void)appendAttributedString:(NSAttributedString *)str
{
    [[_textView textStorage] beginEditing];
    [[_textView textStorage] insertAttributedString:str atIndex:([[_textView textStorage] length] - 1)];
    [[_textView textStorage] endEditing];
    _numberOfLines += [[[str string] componentsSeparatedByString:@"\n"] count] - 1;
    [self maintianScrollback];
    if (!mIsLocked) [self autoscroll];
}

- (void)eraseCharacter
{
    if ([[[_textView textStorage] string] characterAtIndex:[[_textView textStorage] length] - 1] != '\n') {
	[[_textView textStorage] deleteCharactersInRange:
	    NSMakeRange([[_textView textStorage] length] - 1, 1)];
    }
    
}

- (void)eraseLine
{
    NSRange lineRange;
    lineRange = [[[_textView textStorage] string] rangeOfString:@"\n" options:NSBackwardsSearch 
	    range:NSMakeRange(0, [[_textView textStorage] length] - 1)];
    if (lineRange.location == NSNotFound) lineRange.location = 0;
    
    [[_textView textStorage] deleteCharactersInRange:
	    NSMakeRange(lineRange.location, [[_textView textStorage] length] - lineRange.location - 1)];
}

- (NSString *)getLastLine
{
    NSRange lineRange;
    lineRange = [[[_textView textStorage] string] rangeOfString:@"\n" options:NSBackwardsSearch 
	    range:NSMakeRange(0, [[_textView textStorage] length] - 1)];
    if (lineRange.location == NSNotFound) lineRange.location = 0;
    
    return [[[_textView textStorage] string] substringWithRange:
	    NSMakeRange(lineRange.location, [[_textView textStorage] length] - lineRange.location - 1)];
}

- (void)scrollToBottom
{
    [_textView scrollRangeToVisible:NSMakeRange([[_textView textStorage] length], 0)];
}

- (void)clear
{
    [_textView setString:@"\n"];
    _numberOfLines = 0;
}

- (void)beatTimerAction:(id)sender
{
	
}

- (void)blinkAction:(id)userInfo
{
    NSRect      visRect;
    NSRange     glyphRange;
    NSRange     charRange;
    NSRange     linkRange;
    int		linkCount, scanLoc, index;
    id		blinkAttr;
    
    // Find the range of visible characters
    visRect = [[_textView enclosingScrollView] documentVisibleRect];
    glyphRange = [[_textView layoutManager] glyphRangeForBoundingRect:visRect inTextContainer:[_textView textContainer]];
    charRange = [[_textView layoutManager] characterRangeForGlyphRange:glyphRange actualGlyphRange:nil];
    
    // Loop through all the visible characters
    scanLoc = charRange.location;
    while( scanLoc < charRange.location + charRange.length ) {
	// Find the next range of characters with a link attribute
	if( [[_textView textStorage] attribute:@"CantripBlinkAttribute" atIndex:scanLoc effectiveRange:&linkRange] ) {
	    blinkAttr = [[_textView textStorage] attributesAtIndex:linkRange.location longestEffectiveRange:&linkRange inRange:linkRange];
	    if (_blinkState) {
		[[_textView textStorage] addAttribute:NSForegroundColorAttributeName value:
		    ([blinkAttr objectForKey:NSBackgroundColorAttributeName] ? [blinkAttr objectForKey:NSBackgroundColorAttributeName] :
			mBackgroundColor) range:linkRange];
	    }
	    else {
		[[_textView textStorage] addAttribute:NSForegroundColorAttributeName value:[blinkAttr objectForKey:@"CantripBlinkAttribute"] range:linkRange];
	    }
	}
	
	// Even if we didn't find a link, the range returned tells us where to check next
	scanLoc = linkRange.location + linkRange.length;
    }
    
    if (![_scrollView topState]) {
	visRect = [[_top enclosingScrollView] documentVisibleRect];
	glyphRange = [[_top layoutManager] glyphRangeForBoundingRect:visRect inTextContainer:[_top textContainer]];
	charRange = [[_top layoutManager] characterRangeForGlyphRange:glyphRange actualGlyphRange:nil];
	
	// Loop through all the visible characters
	scanLoc = charRange.location;
	while( scanLoc < charRange.location + charRange.length ) {
	    // Find the next range of characters with a link attribute
	    if( [[_top textStorage] attribute:@"CantripBlinkAttribute" atIndex:scanLoc effectiveRange:&linkRange] ) {
		blinkAttr = [[_top textStorage] attributesAtIndex:linkRange.location longestEffectiveRange:&linkRange inRange:linkRange];
		if (_blinkState) {
		    [[_top textStorage] addAttribute:NSForegroundColorAttributeName value:
			([blinkAttr objectForKey:NSBackgroundColorAttributeName] ? [blinkAttr objectForKey:NSBackgroundColorAttributeName] :
			 mBackgroundColor) range:linkRange];
		}
		else {
		    [[_top textStorage] addAttribute:NSForegroundColorAttributeName value:[blinkAttr objectForKey:@"CantripBlinkAttribute"] range:linkRange];
		}
	    }
	    
	    // Even if we didn't find a link, the range returned tells us where to check next
	    scanLoc = linkRange.location + linkRange.length;
	}

    }
    
    _blinkState = !_blinkState;
}

#pragma mark -

const static NSString *ansiStrings[8] = { @"black", @"red", @"green", @"yellow", @"blue",
	@"magenta", @"cyan", @"white" };

- (void)interpretDisplayParams:(unichar)ch
{
    int x, param;
	    
    for (x = 0; x < [_stateMachine numberOfParams]; x++) {
		param = [_stateMachine paramAtIndex:x];
		
		if (param >= 30 && param <= 37) {
			_foreColor = ansiStrings[param - 30];
		} else if (param >= 40 && param <= 47) {
			_backColor = ansiStrings[param - 40];
		} else if (param == 24) {
			_underline = NO;
		} else if (param == 22) {
			_bold = NO;
		} else if (param == 25) {
			_blink = NO;
		} else if (param == 27) {
			_inverse = NO;
		} else {
			switch (param) {
			case 0:
				_bold = _underline = _blink = _inverse = NO; 
				_foreColor = nil; _backColor = nil; break;
			case 1:
				_bold = YES; break;
			case 3:
				break; // italic
			case 4:
				_underline = YES; break;
			case 5:
			case 6:
				_blink = YES; break;
			case 7:
				_inverse = YES; break;
			default:
				break; // Unknown parameter
			}
		}
    }

}

- (void)applyAttributesToString:(NSMutableAttributedString *)string range:(NSRange)range
{
    NSColor *appliedFColor = nil, *appliedBColor = nil;
	
    [_textView setTypingAttributes:[NSDictionary dictionary]];
    [string addAttribute:NSFontAttributeName value:[self font] range:range];
    
    if (_inverse) {
		NSColor *temp = _backColor;
		_backColor = _foreColor;
		_foreColor = temp;
    }
    
    if (_foreColor != nil) {
		if (_bold && mUsesIntenseColors) appliedFColor = [mAnsiIntenseColors objectForKey:_foreColor];
		else appliedFColor = [mAnsiPlainColors objectForKey:_foreColor];
    } else appliedFColor = mTextColor;
    	
    [string addAttribute:NSForegroundColorAttributeName 
		   value:appliedFColor 
		   range:range];
    
    if (_backColor != nil) [string addAttribute:NSBackgroundColorAttributeName 
					  value:[mAnsiPlainColors objectForKey:_backColor] 
					  range:range];
    else [string removeAttribute:NSBackgroundColorAttributeName range:range];
    
    if (_underline) [string addAttribute:NSUnderlineStyleAttributeName 
				   value:[NSNumber numberWithInt:NSUnderlineStyleSingle] 
				   range:range];
	    
    if (_blink) {
	    [string addAttribute:@"CantripBlinkAttribute" value:(_foreColor != nil ? 
		    (_bold && mBoldIntenseColors ? [mAnsiIntenseColors objectForKey:_foreColor] : 
			    [mAnsiPlainColors objectForKey:_foreColor]) : mTextColor) range:range];
    } else [string removeAttribute:@"CantripBlinkAttribute" range:range];
    
    if (_bold && (!mUsesIntenseColors || mBoldIntenseColors)) [string applyFontTraits:NSBoldFontMask range:range];
}

- (void)processString:(NSString *)str
{
    NSColor *_tempBackColor, *_tempForeColor;
    NSFont *_tempFont;
    NSMutableAttributedString *finalString;
    unsigned char c;
    int i, x, n, action, addedLines = 0;
    NSRange modeRange, theRange;
    int response;

    _tempBackColor = [self backgroundColor];
    _tempForeColor = [self textColor];
    _tempFont = [self font];

    modeRange.location = 0;
    modeRange.length = 0;

    finalString = [[[NSMutableAttributedString alloc] init] autorelease];

    [_textView setTypingAttributes:[NSDictionary dictionary]];

    for (i = 0; i < [str length]; i++) {
		c = [str characterAtIndex:i];
		
		// Handle RFC 855 option negotiations
		if (c == 255) { _negotiateState = 1; continue; }
		if (_negotiateState == 1) { 
			if (c >= 251 && c <= 254) _negotiateState = 2; 
			else _negotiateState = 0; 
			continue;
		} /*251..254*/
		if (_negotiateState == 2) { _negotiateState = 0; _opt2 = c; continue; }
		
		action = [_stateMachine step:c];
		
		if (action == ANSI_ACTION_DISPATCH) {
			if (c == 'm') {
				if (mAnsiEnabled) {	
					[self applyAttributesToString:finalString range:modeRange];
					[self interpretDisplayParams:c];
					modeRange.location += modeRange.length;
					modeRange.length = 0;
				}
			} else {
				// no other dispatches recognized currently
				// but this is where you'd put them
			}
		}
		else if (action == ANSI_ACTION_PRINT) {
			[[finalString mutableString] appendFormat:@"%c", c];
			modeRange.length++;
			_column++;
		} else if (action == ANSI_ACTION_EXECUTE) {
			if (c == '\n') {
				[[finalString mutableString] appendString:@"\n"]; modeRange.length++;
				_row++;
				//if (_row == maxRows) _row = maxRows - 1; // <-- ?
				_column = 0;
			} else if (c == '\t')  {
				int spaces = _column % mTabSize;
				spaces = mTabSize - spaces;
				for (x = 0; x < spaces; x++) {
					[[finalString mutableString] appendString:@" "];
					modeRange.length++;
					_column++;
				}
			} else if (c == 7) { // bel
				NSBeep();
			}
		}
    }

    [self applyAttributesToString:finalString range:modeRange];	
    [finalString detectURLs:[NSColor blueColor]];

    if ([self delegate] && [[self delegate] respondsToSelector:@selector(displayView:responseForFormattedString:)]) {
		[[self delegate] displayView:self handleFormattedString:finalString];
    }

    [[_textView textStorage] beginEditing];
    [[_textView textStorage] insertAttributedString:finalString atIndex:([[_textView textStorage] length] - 1)];
    [[_textView textStorage] endEditing];

    addedLines = [[[finalString string] componentsSeparatedByString:@"\n"] count] - 1;
    
    _numberOfLines += addedLines;
    [self maintianScrollback];
		
    if (!mIsLocked) [self autoscroll];
}

- (void)updateColumnForString:(NSString *)string
{
    int i;
    for (i = 0; i < [string length]; i++)
    {
	if ([string characterAtIndex:i] == '\n') _column = 0;
	else _column++;
    }
}

- (void)autoscroll
{
    BOOL shouldScroll = NO;

    if (NSMaxY([_textView visibleRect]) == NSMaxY([_textView bounds]) 
	|| [[[_textView enclosingScrollView] verticalScroller] floatValue] >= .95) 
    shouldScroll = YES;

    if (shouldScroll || !mSensativeAutoScroll) { 
	[_textView scrollRangeToVisible:NSMakeRange([[_textView textStorage] length], 0)];
    }
} 

- (void)maintianScrollback
{
    if (_numberOfLines > (float)mScrollBack * 1.5 && mScrollBack > 0)
    {
	int over = _numberOfLines - mScrollBack, i;
	NSRange removeRange = NSMakeRange(0,0);
	char *ptr = [[[_textView textStorage] string] cString];
	unsigned length = [[_textView textStorage] length];
	for (i = 0; i < length; i++) {
	    removeRange.length++;
	    if (*ptr == '\n') over--;
	    if (!over) break;
	    ptr++;
	}
	[[_textView textStorage] deleteCharactersInRange:removeRange];
	_numberOfLines = mScrollBack;
    }
}


#pragma mark Accessors

- (int)tabSize { return mTabSize; }
- (void)setTabSize:(int)ts { mTabSize = ts; }

- (NSFont *)font { return mFont; }
- (void)setFont:(NSFont*)font { [mFont autorelease];
				mFont = [font copy];
				[_textView setFont:font]; }

- (NSColor *)textColor { return mTextColor; }
- (void)setTextColor:(NSColor*)color { [mTextColor autorelease];
				       mTextColor = [color copy]; }

- (NSColor *)backgroundColor { return mBackgroundColor; }
- (void)setBackgroundColor:(NSColor*)color { [_textView setBackgroundColor:color];
					     [mBackgroundColor autorelease];
					     mBackgroundColor = [color copy];
					     if (_top) [_top setBackgroundColor:color]; }


- (NSDictionary *)ansiPlainColors { return mAnsiPlainColors; }
- (void)setAnsiPlainColors:(NSDictionary*)colors { [mAnsiPlainColors setDictionary:colors]; }

- (NSDictionary *)ansiIntenseColors { return mAnsiIntenseColors; }
- (void)setAnsiIntenseColors:(NSDictionary*)colors { [mAnsiIntenseColors setDictionary:colors]; }

- (BOOL)usesIntenseColors { return mUsesIntenseColors; }
- (void)setUsesIntenseColors:(BOOL)b { mUsesIntenseColors = b; }

- (BOOL)boldIntenseColors { return mBoldIntenseColors; }
- (void)setBoldIntenseColors:(BOOL)b { mBoldIntenseColors = b; }

- (long)scrollBack { return mScrollBack; }
- (void)setScrollBack:(long)s { mScrollBack = s; }

- (BOOL)ansiEnabled { return mAnsiEnabled; }
- (void)setAnsiEnabled:(BOOL)b { mAnsiEnabled = b; }

- (BOOL)sensativeAutoScroll { return mSensativeAutoScroll; }
- (void)setSensativeAutoScroll:(BOOL)b { mSensativeAutoScroll = b; }

- (BOOL)blinkingEnabled { return blinkingEnabled; }
- (void)setBlinkingEnabled:(BOOL)b { 
    blinkingEnabled = b;
    if (b && !_blinkTimer) {
	_blinkTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(blinkAction:) userInfo:nil repeats:YES];
	[_blinkTimer retain];
    } else if (!b && _blinkTimer) {
	[_blinkTimer invalidate];
	[_blinkTimer release];
	_blinkTimer = nil;
	if (!_blinkState) [self blinkAction:nil];
    }
}

- (BOOL)locked { return mIsLocked; }
- (void)setLocked:(BOOL)b { mIsLocked = b; }

- (id)delegate { return delegate; }
- (void)setDelegate:(id)d { delegate = d; }

// Mutator?
- (CantripURLTextView *)textView { return _textView; }

#pragma mark -

- (void)resetCursorRects
{
    [self addCursorRect:[self visibleRect] cursor:[NSCursor arrowCursor]];
}

@end
